/*
 *  Sound.cpp
 *  SoundController0002
 *
 *  Created by jiro on 11/06/16.
 *  Copyright 2011 jiro music. All rights reserved.
 *
 */

#include "Sound.h"

#include "../../sc/SoundGenerator.h"
#include "../../sc/Common/Note.h"
#include "../../sc/Common/WellTemperament.h"
#include "HarmonicSystem.h"
#include <sstream>

namespace sc
{

namespace hs
{

	Sound::Sound()
		: type( Unknown ),
		  midiNumber( ::sc::Note::MidiNumberMute ),
		  dynamics( Note::mezzoForte ),
		  frequency( -1 )
	{}

	Sound::Sound( WaveType type, ::sc::Note::MidiNumber midiNumber )
		: type( type ),
		  midiNumber( midiNumber ),
		  dynamics( Note::mezzoForte ),
		  frequency( -1 )
	{}

	Sound::Sound( WaveType type, ::sc::Note::MidiNumber midiNumber, double dynamics )
		: type( type ),
		  midiNumber( midiNumber ),
		  dynamics( dynamics ),
		  frequency( -1 )
	{}

	Sound::Sound( double frequency )
		: type( SingleSineWave ),
		  midiNumber( sc::Note::MidiNumberMute  ),
		  dynamics( Note::mezzoForte ),
		  frequency( frequency )
	{}

	Sound::Sound( double frequency, double dynamics )
		: type( SingleSineWave ),
		  midiNumber( sc::Note::MidiNumberMute ),
		  dynamics( dynamics ),
		  frequency( frequency )
	{}

	Sound::WaveType Sound::GetWaveType() const
	{
		return type;
	}

	::sc::Note::MidiNumber Sound::GetMidiNumber() const
	{
		return midiNumber;
	}

	double Sound::GetFrequency() const
	{
		if ( ( midiNumber == sc::Note::MidiNumberMute ) &&
			 ( frequency != -1 ) )
		{
			return frequency;
		}
		return WellTemperament::GetFrequency( midiNumber );
	}
	
	double Sound::GetDynamics() const
	{
		return dynamics;
	}

	int Sound::GetHarmonicsCount() const
	{
		if ( ( midiNumber == sc::Note::MidiNumberMute ) &&
			 ( frequency == -1 ) )
		{
			return 0;
		}
		int count = static_cast< int >( HarmonicSystem::HighestFrequency / GetFrequency() );
		return count;
	}

	double Sound::GetHarmonics( int nth ) const
	{
		if ( nth == 0 )
		{
			return 0;
//			throw InvalidParameterException( "double Sound::GetHarmonics( int nth ) const, nth must not be zero" );
		}

		double harmonicFrequency;
		if ( frequency != -1 )
		{
			harmonicFrequency = frequency;
		}
		else
		{
			harmonicFrequency = WellTemperament::GetFrequency( midiNumber );
		}
		if ( nth < 0 )
		{
			return - harmonicFrequency / nth;
		}
		return nth * harmonicFrequency;
	}

	double Sound::GetHarmonicAmplitude( int nth ) const
	{
		if ( nth == 0 )
		{
			return 0.0;
//			throw InvalidParameterException( "double Sound::GetHarmonicAmplitude( int nth ) const, nth must not be zero" );
		}
		if ( nth < 0 )
		{
			nth *= -1;
		}

		switch ( type )
		{
		case SingleSineWave:
			if ( nth == 1 )
			{
				return 1.0;
			}
			return 0.0;

		case SawWave:
			return 1.0 / nth;

		case SquareWave:
			if ( nth == 1 or ( nth % 2 == 1 ) )
			{
				return 1.0 / nth;
			}
			return 0.0;

		case TriangleWave:
			if ( nth == 1 or ( nth % 2 == 1 ) )
			{
				return 1.0 / ( nth * nth );
			}
			return 0.0;

		default:
			throw InvalidParameterException( "HarmonicAmplitude, unknown WaveType" );
		}
	}
	
	double Sound::GetHarmonicWeight( int nth ) const
	{
		return GetHarmonicAmplitude( abs( nth ) );
//		return dynamics * GetHarmonicAmplitude( abs( nth ) );
	}

	bool Sound::ContainHarmonics( double harmonicsFrequency ) const
	{
		double frequency = WellTemperament::GetFrequency( midiNumber );
		if ( harmonicsFrequency < frequency )
		{
			return false;
		}
		for ( int i = 1; i <= GetHarmonicsCount(); i++ )
		{
			if ( fabs( GetHarmonics( i ) - harmonicsFrequency ) < WellTemperament::calculationError )
			{
				return true;
			}
		}
		return false;
	}

	void Sound::GetAllHarmonics( std::vector< double >& harmonics ) const
	{
		for ( int i = 1; i <= GetHarmonicsCount(); i++ )
		{
			harmonics.push_back( GetHarmonics( i ) );
		}
	}

	const Sound::NearbyWellTemperamentNotes& Sound::GetNearbyWellTemperamentNotes()
	{
		if ( not nearbyWellTemeramentNotes.empty() )
		{
			return nearbyWellTemeramentNotes;
		}

		std::vector< double > harmonics;
		GetAllHarmonics( harmonics );
		for ( int i = 1; i < harmonics.size(); i++ )
		{
			double harmonic = GetHarmonics( i );
			for ( int j = sc::Note::CMinus1; j <= sc::Note::G9; j++ )
			{
				double noteFrequency = WellTemperament::GetFrequency( static_cast<sc::Note::MidiNumber>( j ) );

				//	skip note is far away from harmonic
				while ( ( noteFrequency < harmonic ) and
					    ( 2.0 * noteFrequency < harmonic ) and
					    ( fabs( 2.0 * noteFrequency - harmonic ) > HarmonicSystem::HowlFrequencyMax ) )
				{
					j += 12;
					noteFrequency = WellTemperament::GetFrequency( static_cast<sc::Note::MidiNumber>( j ) );
				}

				while ( true )
				{
					if ( fabs( noteFrequency - harmonic ) <= HarmonicSystem::HowlFrequencyMax )
//					if ( ( fabs( noteFrequency - harmonic ) <= HarmonicSystem::HowlFrequencyMax ) and
//						 ( ( midiNumber % 12 ) != ( j % 12 ) ) )
					{
						if ( frequency == -1 )
						{
							nearbyWellTemeramentNotes.insert(
								std::pair< HarmonicSound, sc::Note::MidiNumber >(
									HarmonicSound( SingleSineWave, static_cast< sc::Note::MidiNumber>( midiNumber ), i ),
									static_cast< sc::Note::MidiNumber>( j )
								) );
						}
						else
						{
							nearbyWellTemeramentNotes.insert(
								std::pair< HarmonicSound, sc::Note::MidiNumber >(
									HarmonicSound( frequency, i ),
									static_cast< sc::Note::MidiNumber>( j )
								) );
						}

					}

					j++;
					noteFrequency = WellTemperament::GetFrequency( static_cast<sc::Note::MidiNumber>( j ) );

					if ( ( noteFrequency > harmonic ) and ( noteFrequency - harmonic > HarmonicSystem::HowlFrequencyMax ) )
					{
						break;
					}
				}
			}
		}
		return nearbyWellTemeramentNotes;	
	}

	bool Sound::operator != ( const Sound& rhs ) const
	{
		return not ( * this == rhs );
	}

	bool Sound::operator < ( const Sound& rhs ) const
	{
		if ( type != rhs.type )
		{
			return type < rhs.type;
		}
		if ( midiNumber != rhs.midiNumber )
		{
			return midiNumber < rhs.midiNumber;
		}
		return frequency < rhs.frequency;
	}

	bool Sound::operator == ( const Sound& rhs ) const
	{
		if ( ( type == rhs.type ) and
			 ( midiNumber == rhs.midiNumber ) and
			 ( frequency == rhs.frequency ) )
		{
			return true;
		}
		return false;
	}

	std::string Sound::ToString( int tabCount ) const
	{
		std::stringstream ss;
		std::string tabs;
		for ( int i = 0; i < tabCount; i++ )
		{
			tabs.append( "\t" );
		}
		if ( frequency == -1 )
		{
			ss << tabs 
				<< "sc::hs::Sound:  WaveType: " << ToString( type ) << ",  midiNumber = " << Note::ToString( midiNumber ) 
				<< "(" << WellTemperament::GetFrequency( midiNumber ) << "Hz)";
		}
		else
		{
			ss << tabs 
				<< "sc::hs::Sound:  WaveType: " << ToString( type ) << ", "
				<< "(" << frequency << "Hz)";
		}
		return ss.str();
	}
	
	std::string Sound::ToString( WaveType type )
	{
		switch ( type )
		{
		case SingleSineWave:
			return "SingleSineWave";

		case SawWave:	
			return "SawWave";
			
		case SquareWave:
			return "SquareWave";

		case TriangleWave:
			return "TriangleWave";

		case Unknown:
			return "Unknown";

		default:
			throw InvalidParameterException( "HarmonicAmplitude, unknown WaveType" );
		}
	}

	std::string Sound::ToString( const NearbyWellTemperamentNotes& notes, int harmonicNthMax, bool isCSV, bool displayOctave, bool verBose )
	{
		NearbyWellTemperamentNotes::const_iterator iter = notes.begin();
		std::stringstream ss;
		for ( ; iter != notes.end(); iter++ )
		{
			sc::Note::MidiNumber nearbyMidiNumber = iter->second;
			const HarmonicSound& sound = iter->first;
			int nearbyNoteHarmonicNth = sound.GetNthOfHarmonic();
			double nearbyFreq = sc::WellTemperament::GetFrequency( nearbyMidiNumber );

			if ( ( harmonicNthMax != -1 ) and ( nearbyNoteHarmonicNth > harmonicNthMax ) )
			{
				continue;
			}

			sc::Note::MidiNumber soundMidiNumber = sound.GetMidiNumber();
			double howlFreq;
			double soundFreq;
			if ( sound.frequency == -1 )
			{
				soundFreq = sc::WellTemperament::GetFrequency( soundMidiNumber );
			}
			else
			{
				soundFreq = sound.frequency;
			}
			howlFreq = fabs( nearbyFreq - nearbyNoteHarmonicNth * soundFreq );
			if ( ( not displayOctave ) and ( howlFreq < WellTemperament::calculationError ) )
			{
				continue;
			}

			if ( isCSV and verBose )
			{
				ss
					<< sc::Note::ToString( nearbyMidiNumber ) << "\t" 
					<< nearbyNoteHarmonicNth <<  "\t" 
					<< sc::Note::ToString( soundMidiNumber ) << "\t"
					<< 1.0 / howlFreq << "\t"
					<< sc::WellTemperament::GetFrequency( nearbyMidiNumber ) << "\t"
					<< nearbyNoteHarmonicNth * soundFreq << "\t"
					<< howlFreq << "\t"
					<< "\n";
			}
			else if ( isCSV and not verBose )
			{
				ss
					<< sc::Note::ToString( nearbyMidiNumber ) << "\t" 
					<< nearbyNoteHarmonicNth <<  "\t" 
					<< howlFreq << "\t"
					<< 1.0 / howlFreq << "\t"
					<< "\n";
			}
			else
			{
				ss
					<< "note: " << sc::Note::ToString( nearbyMidiNumber ) << "(" << nearbyFreq << "[Hz])"
					<< " is near by " << sc::Note::ToString( soundMidiNumber ) << "(" << soundFreq << "[Hz])'s "
					<< nearbyNoteHarmonicNth <<  "th(" << nearbyNoteHarmonicNth * soundFreq << "[Hz]), " 
					<< "∂=" << howlFreq << "[Hz], ";
					ss << 1.0 / howlFreq << "[s/howl]\n";
			}
		}
		return ss.str();
	}
	
	std::string Sound::ToString( const std::vector< NearbyWellTemperamentNotes >& notesList, int harmonicNthMax, bool isCSV, bool displayOctave, bool verBose )
	{
		std::stringstream ss; 

		if ( isCSV and verBose )
		{
			ss << "near\t" 
			   << "th\t"
			   << "base\t"
			   << "[s/howl]\t"
			   << "near[Hz]\t"
				<< "harm[Hz]\t"
				<< "Δ[Hz]\n";
//			ss << "note\t[Hz]\tth\t[Hz]\tis near by\t[Hz]\t∂[Hz]\t[s/howl]\n";
		}
		else if ( isCSV and not verBose )
		{
			ss << "near\t" 
			   << "th\t"
			   << "Δ[Hz]\t"
			   << "[s/howl]\n";
		}
		int count = notesList.size();
		for ( int i = 0; i < count; i++ )
		{
			if ( notesList[ i ].empty() )
			{
				ss << std::endl;
				continue;
			}
			ss << ToString( notesList[ i ], harmonicNthMax, isCSV, displayOctave, verBose ) << std::endl;
		} 
		return ss.str();
	}

}

}